/* **************************************************
 * User Group Manager
 *      author:     Baskin Tapkan - baskint@hotmail.com
 *          written for Code Plex
 *              April - 2008
 *                  http://tapkan.com
 * ****************************************************
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.DirectoryServices;
using System.Management;

namespace UserGroupManager
{
    public class GroupMgr : IGroupMgr
    {
        #region "Properties"

        protected string computerName;
        protected string domainName;
        /// <summary>
        /// Computer name accessors
        /// </summary>
        public string ComputerName
        {
            get { return computerName; }
            set { computerName = value; }
        }

        public string DomainName
        {
            get { return domainName; }
            set { domainName = value; }
        }

        #endregion

        /// <summary>
        /// Gets a list of local groups
        /// </summary>
        /// <returns></returns>
        public List<string> GetLocalGroups()
        {
            List<string> groupNames = new List<string>();

            string selectquery = string.Format(
                   "SELECT * FROM Win32_Group WHERE Domain='{0}' AND LocalAccount=-1 AND SID LIKE '%-21-%'",
                      computerName.ToUpper());
            ManagementObjectSearcher mSearcher = GetManagementObject(selectquery);
            if (mSearcher != null)
            {
                try
                {
                    foreach (ManagementObject mObject in mSearcher.Get())
                    {
                        groupNames.Add(mObject["Name"].ToString());
                    }
                }
                catch (Exception ex)
                {
                    throw ex;           // throw the error up the stack
                }
            }
            return groupNames;
        }

        /// <summary>
        /// Gets a dictionary of group names and descriptions
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, string> GetGroupsAndDescriptions()
        {
            Dictionary<string, string> groupsDict = new Dictionary<string, string>();

            string selectquery = string.Format(
                   "SELECT * FROM Win32_Group WHERE Domain='{0}' AND LocalAccount=-1 AND SID LIKE '%-21-%'",
                   computerName.ToUpper());
            ManagementObjectSearcher mSearcher = GetManagementObject(selectquery);
            if (mSearcher != null)
            {
                try
                {
                    foreach (ManagementObject mObject in mSearcher.Get())
                    {
                        groupsDict.Add(mObject["Name"].ToString(), mObject["Description"].ToString());
                    }
                }
                catch (Exception ex)
                {
                    throw ex;           // throw the error up the stack
                }
            }
            return groupsDict;
        }

        /// <summary>
        /// Gets all the members in group
        /// </summary>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public List<string> GetMembersInGroup(string groupName)
        {
            List<string> memberNames = new List<string>();

            DirectoryEntry winNT = new DirectoryEntry("WinNT://" + computerName.ToUpper() + ",computer");

            DirectoryEntry groupEntry = winNT.Children.Find(groupName, "Group");
            object members = groupEntry.Invoke("Members", null);

            try
            {
                foreach (object member in (System.Collections.IEnumerable)members)
                {
                    DirectoryEntry user_groups = new DirectoryEntry(member);

                    string userName = user_groups.Name.ToString();
                    string sPath = user_groups.Path.ToString();

                    // flag to separate AD users from local users: local users carry "<computerName>/" in the Path string
                    if (!sPath.Contains(computerName.ToUpper() + "/"))
                        userName = domainName.ToUpper() + "/" + userName;

                    memberNames.Add(userName);
                    user_groups.Dispose();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                groupEntry.Dispose();
                winNT.Dispose();
            }

            return memberNames;
        }

        /// <summary>
        /// Returns whether the user is a member of this group
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public bool IsMember(string userName, string groupName)
        {
            bool result = false;

            try
            {
                DirectoryEntry objLocalEntry = new DirectoryEntry("WinNT://" + computerName + ",computer");
                DirectoryEntry objUser;
                DirectoryEntry objGroup = objLocalEntry.Children.Find(groupName, "group");

                // handle Domain users!
                if (userName.Contains("/"))
                {
                    userName = "WinNT://" + userName;
                    // handle ad user
                    // DirectoryEntry objADUser = 
                    //  Console.WriteLine("AD user, check AD!");
                    result = (bool)objGroup.Invoke("isMember", new object[] { userName });
                }
                else
                {
                    objUser = objLocalEntry.Children.Find(userName, "user");
                    result = (bool)objGroup.Invoke("isMember", new object[] { objUser.Path.ToString() });
                    objUser.Dispose();
                }

                objGroup.Dispose();
                objLocalEntry.Dispose();

            }
            catch (System.Runtime.InteropServices.COMException ce)
            {
                //return exceptionError(ce);B
                // return error("Group path: " + groupPath + ", User path: " + userPath, true);
                ErrorLogger.LogError(ce.Message, ce.StackTrace);
            }
            catch (Exception ex)
            {
                ErrorLogger.LogError(ex.Message, ex.StackTrace);
            }
            return result;

        }

        public bool CheckGroup(string grpName)
        {
            bool result = false;
            using (DirectoryEntry dirEntry =
                     new DirectoryEntry("WinNT://" + computerName + ",computer"))
            {
                try
                {
                    using (DirectoryEntry grpEntry = dirEntry.Children.Find(grpName, "group"))
                    {
                        if (grpEntry != null)
                            result = true;
                    }
                }
                catch
                {
                    return result;
                }
            }
            return result;
        }

        /// <summary>
        /// Purges all the members from a group
        /// </summary>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public bool PurgeMembers(string groupName)
        {
            // first get a list of users in group - self call

            bool result = false;
            List<string> memberList = GetMembersInGroup(groupName);

            foreach (string member in memberList)
            {
                RemoveMember(groupName, member);
            }

            return result;
        }

        private void RemoveMember(string groupName, string userName)
        {
            using (DirectoryEntry dirEntry =
                      new DirectoryEntry("WinNT://" + computerName + ",computer"))
            {

                using (DirectoryEntry grpEntry = dirEntry.Children.Find(groupName, "group"))
                {
                    if (grpEntry != null)
                    {
                        string memberPath = string.Format("WinNT://{0}/{1}", computerName, userName);
                        grpEntry.Invoke("Remove", new object[] { memberPath });
                    }
                    // activate changes
                    grpEntry.CommitChanges();
                }
            }
        }

        /// <summary>
        /// Adds members to a given group
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="memberList"></param>
        /// <returns></returns>
        public bool AddMembers(string groupName, List<string> memberList)
        {
            bool result = false;
            foreach (string member in memberList)
            {
                using (DirectoryEntry dirEntry =
                      new DirectoryEntry("WinNT://" + computerName + ",computer"))
                {
                    using (DirectoryEntry grpEntry = dirEntry.Children.Find(groupName, "group"))
                    {
                        if (grpEntry != null)
                        {
                            string memberPath = string.Format("WinNT://{0}/{1}", computerName, member);
                            grpEntry.Invoke("Add", new object[] { memberPath });
                        }
                        // activate changes
                        grpEntry.CommitChanges();
                        result = true;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Creates a new group on the target system
        /// </summary>
        /// <param name="grpName"></param>
        /// <param name="grpDesc"></param>
        /// <returns></returns>
        public bool CreateNewGroup(string grpName, string grpDesc)
        {
            bool result = false;
            DirectoryEntry dirEntry =
                new DirectoryEntry("WinNT://" + computerName + ",computer");
            try
            {
                DirectoryEntry de = dirEntry.Children.Add(grpName, "group");
                de.InvokeSet("Description", grpDesc);
                de.CommitChanges();
                result = true;
            }
            catch (Exception ex)
            {
                result = false;
                throw ex;
            }

            return result;
        }

        public bool EditGroup(string oldGrpName, string newGrpName, string oldGrpDesc, string newGrpDesc)
        {
            bool result = false;

            string dirEntryString = "WinNT://" + computerName + ",computer";

            if (!CheckforValidDirectoryEntry(dirEntryString))
            {
                result = false;
                return result;
            }

            DirectoryEntry dirEntry = new DirectoryEntry(dirEntryString);
            try
            {
                using (dirEntry)
                {
                    DirectoryEntry de = dirEntry.Children.Find(oldGrpName, "group");

                    de.Properties["description"].Value = newGrpDesc;
                    de.CommitChanges();
                    de.Dispose();
                    if (!oldGrpName.Equals(newGrpName))
                    {
                        ManagementObject theInstance = new ManagementObject("root\\CIMv2", "Win32_Group.Domain='" + computerName +
                            "',Name='" + oldGrpName  + "'", null);

                        ManagementBaseObject inputParams = theInstance.GetMethodParameters("Rename");
                        inputParams.SetPropertyValue("Name", newGrpName);
                        ManagementBaseObject outParams = theInstance.InvokeMethod("Rename", inputParams, null);
                    }

                  
                }
                result = true;
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return result;

        }

        /// <summary>
        /// Deletes an existing group on the target system
        /// </summary>
        /// <param name="grpName"></param>
        /// <returns></returns>
        public bool DeleteGroup(string grpName)
        {
            bool result = false;
            string dirEntryString = "WinNT://" + computerName + ",computer";
            if (!CheckforValidDirectoryEntry(dirEntryString))
            {
                result = false;
                return result;
            }

            try
            {
                // create a new directory entry object 
                DirectoryEntry dirEntry =
                        new DirectoryEntry(dirEntryString);

                // locate the userEntry node in the created directory object
                DirectoryEntry grpEntry = dirEntry.Children.Find(grpName, "group");
                // remove the found entry from the nodes
                dirEntry.Children.Remove(grpEntry);
                //dirEntry.CommitChanges();
                //grpEntry.Dispose();
                dirEntry.Dispose();
                result = true;
            }
            catch (Exception ex)
            {
                result = false;
                ErrorLogger.LogError(ex.Message, ex.StackTrace);
            }
            return result;
        }

        #region "Helper Methods"

        /// <summary>
        /// Removes a given user from all groups
        /// </summary>
        /// <param name="userName"></param>
        private void RemoveMemberFromAllGroups(string userName)
        {
            List<string> groupList = GetLocalGroups();
            foreach (string groupName in groupList)
            {
                RemoveMember(groupName, userName);
            }
        }


        // checks for a valid directory entry
        private bool CheckforValidDirectoryEntry(string dirEntryString)
        {
            bool result = false;
            try
            {
                if (DirectoryEntry.Exists(dirEntryString))
                    result = true;
                else
                    result = false;
            }
            catch (Exception ex)
            {
                result = false;
                ErrorLogger.LogError(ex.Message, ex.StackTrace);
            }
            return result;
        }

        private ManagementObjectSearcher GetManagementObject(string selectQuery)
        {
            ManagementObjectSearcher mSearcher = new ManagementObjectSearcher(); ;
            try
            {
                // if-clause to check whether the execution is on done on local computer
                if (System.Environment.MachineName.Equals(computerName.ToUpper()))
                {
                    // for localmachine - note that "SelectQuery" value is different than local machine
                    //  SelectQuery sQuery = new SelectQuery("Win32_UserAccount", "Domain='" + computerName + "'");
                    SelectQuery sQuery = new SelectQuery(selectQuery);
                    mSearcher = new ManagementObjectSearcher(sQuery);
                    return mSearcher;
                }
                else
                {
                    // for remote machine -- need to change scope
                    string scopeString = String.Format("\\\\{0}\\root\\cimv2", computerName);
                    ManagementScope scope = new ManagementScope(scopeString);
                    SelectQuery sQuery = new SelectQuery(selectQuery);
                    scope.Connect();
                    mSearcher = new ManagementObjectSearcher(scope, sQuery);
                    return mSearcher;
                }
            }
            catch (Exception ex)
            {
                ErrorLogger.LogError(ex.Message, ex.StackTrace);
                return mSearcher;
            }
        }

        #endregion
    }
}
